package games.mapacman.server;

import games.mapacman.common.Dot;
import games.mapacman.common.EatenSign;
import games.mapacman.common.Fruit;
import games.mapacman.common.Powerpill;
import games.mapacman.common.Superdot;
import games.mapacman.common.ZoneChangePoint;
import games.mapacman.common.consts;


import java.awt.Point;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Random;
import java.util.Vector;

import marauroa.common.Log4J;
import marauroa.common.game.RPObject;
import marauroa.server.game.rp.MarauroaRPZone;

public class MaPacmanZone extends MarauroaRPZone {
	private static final marauroa.common.Logger logger = Log4J.getLogger(MaPacmanZone.class);
	private int x;
	private int y;
	private boolean[][] collision;
	private String zonename;
	private MaPacmanRPWorld world;
	private Dot[][] dots;
	private Vector <RPObject> Ghosts;
	private Vector<Dot> respawnDots;
	private Vector<RPObject> Players;
	private Vector<Point> RespawnPoints;
	private Vector<Point> GhostRespawnPoints;
	private Vector<Point> GhostBlock;
	private Vector<EatenSign> EatenSigns;
	private Vector<ZoneChangePoint> zoneChangePoints;
	private Vector<String> zoneChangeNames;
	private Vector<zoneChangeNote> zoneChangeNotes;
	private Vector<RPObject> addMovablesNextTurn;
	private Random randomgen; 
	
	private int dot_respawnTime = 150;
	private int superdot_respawnTime = 350;
	private int fruit_respawnTime = 500;
	private int powerpill_respawnTime = 700;
	private int dot_score=1;
	private int superdot_score=5;
	private int fruit_score=50;
	private int powerpill_score=0;
	// Probability that SuperDot is Superdot ... otherwise it's normal dot
	// prob=1 means every sdot is superdot ... prob =3 means every third Sdot is Sdot
	private int superdot_prob=1;
	private int fruit_prob=30;
	private int powerpill_prob=1;

	
	public MaPacmanZone(String zoneName, MaPacmanRPWorld world) {
		super(zoneName);
		this.randomgen = world.getRandom();
		this.Ghosts = new Vector<RPObject>();
		this.addMovablesNextTurn = new Vector<RPObject>();
		this.respawnDots = new Vector<Dot>();
		this.Players = new Vector<RPObject>();
		this.RespawnPoints = new Vector<Point>();
		this.GhostRespawnPoints = new Vector<Point>();
		this.GhostBlock = new Vector<Point>();
		this.EatenSigns = new Vector<EatenSign>();
		this.zoneChangeNames = new Vector<String>();
		this.zoneChangePoints= new Vector<ZoneChangePoint>();
		// save zoneChanges here to do when turn ends
		this.zoneChangeNotes= new Vector<zoneChangeNote>();
		this.zonename=zoneName;
		this.world=world;
		world.addRPZone(this);		
		
	}
	
	public void initialize()
	{	/*
		# Wall
		* Dot
		g Ghost without Dot
		G Ghost with Dot
		r respawn without Dot
		R respawn with Dot
		F Fruit
		P Powerpill
		0 to 9 ZoneChange Points
		- Block without Dot
		_ Block with Dot
		+ Superdot
		*/
		
		try
		{
		BufferedReader map = new BufferedReader(new FileReader("games/mapacman/server/maps/"+zonename+".map"));
		
		
		int x=100;
		int y =100;
		boolean mapstart=false;
		String line;
		int ypos=0;
		while( (line = map.readLine()) !=null )
		{
			if (mapstart)
			{
				for (int xpos=0; xpos<line.length();xpos++)
				{
					char currentItem =line.charAt(xpos);
					if (currentItem =='#')
					{	// is WALL
						collision[xpos][ypos]=true;
						RPObject wall = new RPObject();
						wall.put("type",consts.TYPE_WALL);
						wall.put("x",xpos);
						wall.put("y",ypos);
						wall.put("zoneid",this.zonename);
						world.add(wall);
					}
					else
					{	// NO WALL
						collision[xpos][ypos]=false;
						if (currentItem =='G' || currentItem =='g' )
						{	// is GHOST
							if (currentItem =='G') currentItem='*';
							RPObject ghost = new RPObject();
							ghost.put("type",consts.TYPE_GHOST);
							ghost.put("x",xpos);
							ghost.put("y",ypos);
							ghost.put("dir",consts.DIR_NORTH);
							ghost.put("nextdir",consts.DIR_NORTH);
							ghost.put("!changedir",10);
							ghost.put("zoneid",this.zonename);
							ghost.put("color",randomgen.nextInt(4));
							Ghosts.add(ghost);
							world.add(ghost);
							GhostRespawnPoints.add(new Point(xpos,ypos));
						}
						else if (currentItem =='R' || currentItem =='r' )
						{	// is RespawnPoint
							if (currentItem =='R') currentItem='*';
							RespawnPoints.add(new Point(xpos,ypos));					
						}
						else if (currentItem =='-' || currentItem =='_' )
						{	// is GhostBlock
							if (currentItem =='_') currentItem='*';
							GhostBlock.add(new Point(xpos,ypos));					
						}
						else if (currentItem =='+')
						{	// is SuperDot
							Dot sdot = new Superdot(xpos,ypos,dot_score,superdot_score,superdot_prob,this);
							dots[xpos][ypos]= sdot;
							world.add(sdot.getRPObject());
						}
						else if (currentItem =='P')
						{	// is Powerpill
							Dot pill = new Powerpill(xpos,ypos,dot_score,powerpill_score,powerpill_prob,this);
							dots[xpos][ypos]= pill;
							world.add(pill.getRPObject());
						}
						else if (currentItem =='F')
						{	// is Fruit
							Dot fruit = new Fruit(xpos,ypos,dot_score,fruit_score,fruit_prob,this);
							dots[xpos][ypos]= fruit;
							world.add(fruit.getRPObject());
						}
						
						
						//check for Dot at last because other Items can have Dot below
						if (currentItem =='*')
						{	// is DOT
							Dot dot = new Dot(xpos,ypos,dot_score,this);
							dots[xpos][ypos]= dot;
							world.add(dot.getRPObject());
						}
						else
						{	// ZoneChange Point
							if (currentItem>=48 && currentItem<=57)
							{
								ZoneChangePoint point = new ZoneChangePoint(this,zoneChangeNames.get(currentItem-48),xpos,ypos);
								zoneChangePoints.add(point);
								world.add(point.getRPObject());			
							}
						}
					}
				}
				ypos++;
			}
			else // MAP INFO didn't start yet
			{
				if(line.startsWith("x="))
				{
					x=Integer.parseInt(line.substring(2));
				}
				else if(line.startsWith("y="))
				{
					y=Integer.parseInt(line.substring(2));
				}
				else if(line.startsWith("y="))
				{
					y=Integer.parseInt(line.substring(2));
				}
				else if(line.startsWith("dot_respawnTime="))
				{
					dot_respawnTime=Integer.parseInt(line.substring("dot_respawnTime=".length()));
				}
				else if(line.startsWith("superdot_respawnTime="))
				{
					superdot_respawnTime=Integer.parseInt(line.substring("superdot_respawnTime=".length()));
				}
				else if(line.startsWith("fruit_respawnTime="))
				{
					fruit_respawnTime=Integer.parseInt(line.substring("fruit_respawnTime=".length()));
				}
				else if(line.startsWith("powerpill_respawnTime="))
				{
					powerpill_respawnTime=Integer.parseInt(line.substring("powerpill_respawnTime=".length()));
				}
				else if(line.startsWith("dot_Score="))
				{
					dot_score=Integer.parseInt(line.substring("dot_Score=".length()));
				}
				else if(line.startsWith("superdot_Score="))
				{
					superdot_score=Integer.parseInt(line.substring("superdot_Score=".length()));
				}
				else if(line.startsWith("fruit_Score="))
				{
					fruit_score=Integer.parseInt(line.substring("fruit_Score=".length()));
				}
				else if(line.startsWith("powerpill_Score="))
				{
					powerpill_score=Integer.parseInt(line.substring("powerpill_Score=".length()));
				}
				else if(line.startsWith("mapchange="))
				{
					zoneChangeNames.add(line.substring("mapchange=".length()));
				}
				else if(line.startsWith("superdot_probability="))
				{
					superdot_prob=Integer.parseInt(line.substring("superdot_probability=".length()));
				}
				else if(line.startsWith("fruit_probability="))
				{
					fruit_prob=Integer.parseInt(line.substring("fruit_probability=".length()));
				}
				else if(line.startsWith("powerpill_probability="))
				{
					powerpill_prob=Integer.parseInt(line.substring("powerpill_probability=".length()));
				}
				else if(line.startsWith("MAPSTART"))
				{	
					mapstart=true;
					collision = new boolean[x][y];
					dots = new Dot[x][y];
				}
			}
		}
		}
		catch (Exception e)
		{
		//TODO: throw the exception to the caller so it knows about the problem.
			e.printStackTrace();
		}
	}
	
	@Override
	public void onInit() throws Exception {
		System.out.println("init zone :"+zonename);
	}

	@Override
	public void onFinish() throws Exception {
		// TODO Auto-generated method stub

	}

	public int isCollision(int x, int y) {
		// order is important : GhostBlock needs to be higher than Player
		if (collision[x][y]) return consts.COLLISION_WALL;
		for (Point block : GhostBlock)
		{
			if(block.getX()==x && block.getY()==y) return consts.COLLISION_GHOSTBLOCK;
		}
		for(RPObject player : Players)
		{
			if(player.getInt("x")==x && player.getInt("y")==y ) return consts.COLLISION_PLAYER;
		}
		for(RPObject ghost : Ghosts)
		{
			if(ghost.getInt("x")==x && ghost.getInt("y")==y ) return consts.COLLISION_GHOST;
		}
		return consts.COLLISION_NONE;
	}
	
	
	public Dot eatDot(int x, int y) {
		if (dots[x][y]!= null)
		{
			Dot res = dots[x][y];
			dots[x][y] = null;
			return res;
		}
		return null;

	}

	public void respawnDot(Dot object) {
		dots[object.getX()][object.getY()] = object;		
	}
	
	public Point getFreeRespawnPoint(int type)
	{

		Random random = new Random(); 
		for (int i=0;i<RespawnPoints.size();i++)
		{
			Point point;
			// type = 0 is Player
			if (type == 0) point = RespawnPoints.get(random.nextInt(RespawnPoints.size()));
			else point = GhostRespawnPoints.get(random.nextInt(GhostRespawnPoints.size()));
			boolean free = true;
			for (RPObject player : Players)
			{
				if (player.getInt("x")== point.getX() && player.getInt("y")==point.getY())
				{
					free = false;
					break;
				}
			}
			for (RPObject ghost : Ghosts)
			{
				if (ghost.getInt("x")== point.getX() && ghost.getInt("y")==point.getY())
				{
					free = false;
					break;
				}
			}
			if (free) return point;
		}
		// if we are here, no free RespawnPoint has been found
		
		return null;
	}
	
	public boolean addPlayer(RPObject object)
	{
		boolean res = respawnMovable(object);
		if (res)
			Players.add(object);
		return res;
				
	}
	
	private boolean respawnMovable(RPObject object) {
		logger.debug("respawnMovable object:" + object);
		Point respawnPoint = getFreeRespawnPoint(object.get("type").equals(consts.TYPE_PLAYER) ? 0 : 1);
		if (respawnPoint!=null)
		{
			object.put("x",(int)respawnPoint.getX());
			object.put("y",(int)respawnPoint.getY());
			object.put("dir",consts.DIR_NONE);
			object.put("nextdir",consts.DIR_NONE);
			object.put("zoneid",this.zonename);
			
			return true;
		}
		else
		{
			// try to add Player in next Turn
			addMovablesNextTurn.add(object);
			return false;
		}		
	}

	public void beginTurn()
	{

		
		// check for Dots to respawn
		Vector<Dot> deleteDots = new Vector<Dot>();
		for(Dot dot: respawnDots)
		{
			if (dot.respawn()) 
			{	// add dot to Vector to delete later - can't delete while in for()
				deleteDots.add(dot);
				respawnDot(dot);
				world.modify(dot.getRPObject());
			}		
		}
		// delete respawned Dots from Vector
		for(Dot object: deleteDots)
		{
			respawnDots.remove(object);
		}
		
		// move Players and Ghosts
		movePlayers();
		moveGhosts();
		
		// change Zones
		for(zoneChangeNote note : zoneChangeNotes)
		{
			world.changeZone(note.object,this, note.newZone.getZoneName());
		}
		zoneChangeNotes.clear();
		
		// add Players that could not been added
		Vector<RPObject> temp = new Vector<RPObject>();
		for (RPObject movable: addMovablesNextTurn)
		{
			respawnMovable(movable);
		}
		addMovablesNextTurn.clear();
		for (RPObject player: temp)
		{
			addPlayer(player);
		}
		
		Vector<EatenSign> signtemp = new Vector<EatenSign>();
		// check EatenSigns
		for (EatenSign sign : EatenSigns)
		{
			if (sign.checkDelete())
			{
				world.remove(sign.getRPObject().getID());
				signtemp.add(sign);
			}
		}
		for (EatenSign sign : signtemp)
		{
			EatenSigns.remove(sign);
		}
	}
	
	public void movePlayers()
	{
		for(RPObject object: Players)
		{
			// if Player is in powermode reduce powerturntime
			if (object.getInt("power")>0)
				object.put("power",object.getInt("power")-1);
			
			int nextDir = object.getInt("nextdir");
			int dir = object.getInt("dir");

			if (nextDir!=consts.DIR_NONE)
			{
				int collision = consts.COLLISION_NONE;
				int x = object.getInt("x");
				int y = object.getInt("y"); 
				switch(nextDir)
				{
					case consts.DIR_NORTH : collision = isCollision(x,y-1); break;
					case consts.DIR_SOUTH : collision = isCollision(x,y+1); break;
					case consts.DIR_EAST : collision = isCollision(x+1,y); break;
					case consts.DIR_WEST : collision = isCollision(x-1,y); break;
				}
				
				if (collision == consts.COLLISION_NONE || collision == consts.COLLISION_GHOSTBLOCK)
				{
					object.put("dir",nextDir);
					dir=nextDir;
					object.put("nextdir",consts.DIR_NONE);
				}
				else
				{   
					// if Player is not moving and want to move in a direction which is blocked. Stop all movements
					if(dir==consts.DIR_NONE)
					{
						object.put("nextdir",consts.DIR_NONE);
						object.put("dir",consts.DIR_NONE);
					}
				}
			}
			
			switch(dir)
			{
				case consts.DIR_NORTH :  movePlayer(object,0,-1); break;
				case consts.DIR_SOUTH : movePlayer(object,0,1); break;
				case consts.DIR_EAST : movePlayer(object,1,0);  break;
				case consts.DIR_WEST : movePlayer(object,-1,0);  break;
				case consts.DIR_NONE : movePlayer(object,0,0);  break;
			}
			world.modify(object);
		}
	}
	
	private void movePlayer(RPObject object, int x, int y)
	{
		int collisionType = isCollision(object.getInt("x")+x,object.getInt("y")+y);
		int newX = object.getInt("x")+x;
		int newY = object.getInt("y")+y;
		
		// if no movement or no collision
		if ( (x==0 && y==0) || collisionType == consts.COLLISION_NONE || collisionType == consts.COLLISION_GHOSTBLOCK)
		{		
			Dot dot;
			if ((dot=eatDot(newX,newY))!=null) 
			{
				dot.eaten(object);
				dot.addStats(world.getStats());
				object.put("score",object.getInt("score")+dot.getScore());
				dot.setRespawnTime();
				respawnDots.add(dot);
				world.modify(dot.getRPObject());
			}
			else
			{
				for (ZoneChangePoint zonechange : zoneChangePoints)
				{
					if (zonechange.isPlacedAt(newX,newY))
					{ // add to change Zone atfer turn
						zoneChangeNotes.add(new zoneChangeNote(object,zonechange));
					}
				}
			}
			object.put("x",newX);
			object.put("y",newY);
		}
		else
		{	
			switch (collisionType)
			{
			case consts.COLLISION_WALL : 
			case consts.COLLISION_PLAYER : object.put("dir",consts.DIR_NONE);break;
			case consts.COLLISION_GHOST : 	
				if (object.getInt("power")>0)
				{ // Player is in Powermode
					object.put("x",newX); 
					object.put("y",newY); 
					killGhostAt(newX,newY);
					Dot dot;
					if ((dot=eatDot(newX,newY))!=null) 
					{
						dot.eaten(object);
						dot.addStats(world.getStats());
						object.put("score",object.getInt("score")+dot.getScore());
						dot.setRespawnTime();
						respawnDots.add(dot);
						world.modify(dot.getRPObject());
					}
				}
				else
				{
					object.put("x",newX); 
					object.put("y",newY); 
					world.modify(object); 
					killPlayer(object); 
					break;
				}											
			}
		}
		world.modify(object);
	}
	
	private void killGhostAt(int newX, int newY) {
		for (RPObject ghost : Ghosts)
		{
			if (ghost.getInt("x")== newX && ghost.getInt("y")== newY)
				killGhost(ghost);
		}
		
	}

	private void killGhost(RPObject object) {
		world.getStats().add("killed Ghost",1);
		EatenSign sign = new EatenSign(object.getInt("x"),object.getInt("y"),this);
		EatenSigns.add(sign);	
		world.add(sign.getRPObject());
		respawnMovable(object);
	}

	private void killPlayer(RPObject object) {
		int x = object.getInt("x");
		int y = object.getInt("y");
		world.getStats().add("killed Player",1);
		int score = object.getInt("score");
		int newPlayerScore = (int)(score-score/10);
		// if more than 200 Points are lost ... only take 200
		if (newPlayerScore<score-200) newPlayerScore = score-200;
		object.put("score",newPlayerScore);

		respawnMovable(object);
		
		object.put("dir",consts.DIR_NONE);
		world.modify(object);
		
		EatenSign sign = new EatenSign(x,y,this);
		EatenSigns.add(sign);
		world.add(sign.getRPObject());
	}

	public void moveGhosts()
	{
		// save CPU ?? don't move ghosts when no Player in world
		if (!world.moveGhosts())
			return;	
		
		for (RPObject ghost : Ghosts)
		{
			// check if Ghost should change Direction
			if (ghost.getInt("!changedir")==0)
			{
				ghost.put("dir",randomgen.nextInt(4));
				ghost.put("!changedir",randomgen.nextInt(6)+1);
			}
			else
			{
				ghost.put("!changedir",ghost.getInt("!changedir")-1);
			}
			
			// move Ghost
			int nextDir = ghost.getInt("nextdir");
			if (nextDir!=consts.DIR_NONE)
			{
				int collision = consts.COLLISION_NONE;
				int x = ghost.getInt("x");
				int y = ghost.getInt("y"); 
				switch(nextDir)
				{
					case consts.DIR_NORTH : collision = isCollision(x,y-1); break;
					case consts.DIR_SOUTH : collision = isCollision(x,y+1); break;
					case consts.DIR_EAST : collision = isCollision(x+1,y); break;
					case consts.DIR_WEST : collision = isCollision(x-1,y); break;
				}
				
				if (collision == consts.COLLISION_NONE || collision == consts.COLLISION_PLAYER)
				{
					ghost.put("dir",nextDir);
					ghost.put("nextdir",consts.DIR_NONE);
				}

			}
			
			switch(ghost.getInt("dir"))
			{
				case consts.DIR_NORTH :  moveGhost(ghost,0,-1); break;
				case consts.DIR_SOUTH : moveGhost(ghost,0,1); break;
				case consts.DIR_EAST : moveGhost(ghost,1,0);  break;
				case consts.DIR_WEST : moveGhost(ghost,-1,0);  break;
			}
			
			world.modify(ghost);
		}
	}

	private void moveGhost(RPObject ghost, int x, int y) {
		int collisionType = isCollision(ghost.getInt("x")+x,ghost.getInt("y")+y);
		if (collisionType == consts.COLLISION_NONE || collisionType == consts.COLLISION_PLAYER )
		{
			int newX = ghost.getInt("x")+x;
			int newY = ghost.getInt("y")+y;
			ghost.put("x",newX);
			ghost.put("y",newY);
			if (collisionType==consts.COLLISION_PLAYER)
			{
				for (RPObject player : Players)
				{
					if (player.getInt("x")==newX && player.getInt("y")==newY)
					{
						if (player.getInt("power")>0)
						{ // Player is in Powermode
							ghost.put("x",newX); 
							ghost.put("y",newY);
							world.modify(ghost);
							killGhostAt(newX,newY);
						}
						else
						{
							killPlayer(player); 
							break;
						}	
						break;
					}
				}
			}
		}
		else
		{
				ghost.put("!changedir",0);			
		}
		
	}

	public void removePlayer(RPObject player) {
		Players.remove(player);
		
	}
	
	public String getName()
	{
		return zonename;
	}

	public Vector<RPObject> getPlayers() {
		return Players;
	}

	public boolean hasPlayers() {
		
		return (getPlayers().size()>0);
	}

	public int getDotRespawnTime() {
		return dot_respawnTime;
	}
	
	public int getSuperDotRespawnTime() {
		return superdot_respawnTime;
	}

	public Random getRandom() {
		return randomgen;
	}

	public int getPowerpillRespawnTime() {
		return powerpill_respawnTime;
	}

	public int getFruitRespawnTime() {
		return fruit_respawnTime;
	}

	public MaPacmanRPWorld getWorld() {
		return world;
	}

}


class zoneChangeNote
{
	ZoneChangePoint newZone;
	RPObject object;
	
	public zoneChangeNote(RPObject object, ZoneChangePoint newZone)
	{
		this.newZone = newZone;
		this.object = object;
	}
}